#include "iniextio.hh"
#include <string>
#include <string.h>
#include <math.h>
#include <memory>
#include <arpa/inet.h>
#include "jkutil/iniparser.h"
#include "jkutil/configparser.h"
#include "log/zuclog.h"
#include "ini/zucIniFile.hh"
#include "zuc/motion/usrmotintf.h"
#include "zuc/motion/motion.h"

#define ZLOG_TAG "EXTIO"

using namespace std;
using namespace Util;

#define _ELOG(_FMT, ...) zlog_limit(zlog_error(_FMT, ##__VA_ARGS__), 1e3);
#define _DLOG(_FMT, ...) zlog_limit(zlog_debug(_FMT, ##__VA_ARGS__), 1e4);

ExtioConfig::ExtioConfig() {}
int ExtioConfig::loadExtIoSetup(const char* cfgfile, ExtIOSetup* extio, size_t cnt)
{
    ConfigParser parser(cfgfile);
    if (!parser.is_valid())
    {
        _ELOG("extio cfgfile err");
        return -1;
    }

    //遍历ExtioMod生成数据结构
    size_t okCnt = 0;
    for (int extioIndex = 0; extioIndex < MAX_EXTIO_CNT; extioIndex++)
    {
        ExtIOSetup setup;
        memset(&setup, 0, sizeof(setup));
        string exp = ConfigParser::str_append_index("ExtioMod", extioIndex);

        string name = "Unknown";
        if (parser.get(exp + ".Name", name, true))
        {
            break;
        }
        strncpy(setup.modName, name.c_str(), MAX_EXTIO_NAME_LEN);

        string modInfo = "Unknown";
        parser.get(exp + ".ExtIOModInfo", modInfo, true);
        strncpy(setup.ExtIOModInfo, modInfo.c_str(), MAX_EXTIO_NAME_LEN);

        int type = 0;
        parser.get(exp + ".Comm.Type", type, true);
        setup.extType = type;
        int commuRet = -1;

        switch (type)
        {
        case EXTIO_COMMU_MBRTU: {
            ModRtuComm comm;
            memset(&comm, 0, sizeof(comm));
            commuRet = loadRtuComm(&parser, comm, extioIndex);
            setup.commInfo.rtuComm = comm;
            if (commuRet)
            {
                _ELOG("read %s MBRTU config error %d", name.c_str(), commuRet);
            }
            break;
        }
        case EXTIO_COMMU_MBTCP: {
            NetComm tcp;
            memset(&tcp, 0, sizeof(tcp));
            commuRet = loadTcpComm(&parser, tcp, extioIndex);
            setup.commInfo.tcpComm = tcp;
            if (commuRet)
            {
                _ELOG("read %s MBTCP config error %d", name.c_str(), commuRet);
            }
            break;
        }
        default:
            setup.extType = EXTIO_COMMU_UNKNOWN;
            _ELOG("%s type unknown", name.c_str(), type);
            break;
        }

        for (int pinIndex = 0; pinIndex < 4; pinIndex++)
        {
            string pinExp = ConfigParser::str_append_index(exp + ".PinSetup.Pin", pinIndex);
            string pinType;
            parser.get(pinExp + ".Type", pinType, true);
            PinSetup pinConfig;
            memset(&pinConfig, 0, sizeof(pinConfig));
            parser.get(pinExp + ".StartRegAddr", pinConfig.startRegAddr, true);
            parser.get(pinExp + ".Number", pinConfig.pinNum, true);
            if (pinType == "di")
                setup.diSetup = pinConfig;
            else if (pinType == "do")
                setup.doSetup = pinConfig;
            else if (pinType == "ai")
                setup.aiSetup = pinConfig;
            else if (pinType == "ao")
                setup.aoSetup = pinConfig;
            else
            {
                _ELOG("%s has unknown PinSetup %s", name.c_str(), pinType.c_str());
            }
        }
        extio[extioIndex] = setup;
        okCnt++;
    }
    return okCnt;
}
int ExtioConfig::saveExtIoSetup(const char* cfgfile, const ExtIOSetup* extio, size_t cnt)
{
    //TODO:
    string name = cfgfile + string(".bak");
    ConfigParser parser(name.c_str());
    parser.clear_file();
    parser.create_file("ExtioSetup");

    parser.add("Version", EXTIO_CONFIG_VERSION, true);
    size_t index = 0;
    for (index = 0; index < cnt; index++)
    {
        const ExtIOSetup* cfg = extio + index;
        if (strlen(cfg->modName) == 0)
        {
            break;
        }
        string exp = ConfigParser::str_append_index("ExtioMod", index);
        parser.add(exp + ".Name", cfg->modName, true);
        parser.add(exp + ".ExtIOModInfo", cfg->ExtIOModInfo, true);
        switch (cfg->extType)
        {
        case EXTIO_COMMU_MBRTU:
            saveRtuComm(&parser, cfg->commInfo.rtuComm, index);
            break;
        case EXTIO_COMMU_MBTCP:
            saveTcpComm(&parser, cfg->commInfo.tcpComm, index);
            break;
        default:
            break;
        }
        exp += ".PinSetup.Pin";
        parser.add(exp + "[0].Type", "di", true);
        parser.add(exp + "[0].StartRegAddr", cfg->diSetup.startRegAddr, true);
        parser.add(exp + "[0].Number", cfg->diSetup.pinNum, true);
        parser.add(exp + "[1].Type", "do", true);
        parser.add(exp + "[1].StartRegAddr", cfg->doSetup.startRegAddr, true);
        parser.add(exp + "[1].Number", cfg->doSetup.pinNum, true);
        parser.add(exp + "[2].Type", "ai", true);
        parser.add(exp + "[2].StartRegAddr", cfg->aiSetup.startRegAddr, true);
        parser.add(exp + "[2].Number", cfg->aiSetup.pinNum, true);
        parser.add(exp + "[3].Type", "ao", true);
        parser.add(exp + "[3].StartRegAddr", cfg->aoSetup.startRegAddr, true);
        parser.add(exp + "[3].Number", cfg->aoSetup.pinNum, true);
    }
    parser.add("ExtioCount", (int)index);
    // remove(cfgfile);
    rename(name.c_str(), cfgfile);
    return index;
}
int ExtioConfig::loadRtuComm(Util::ConfigParser* parser, ModRtuComm& rtu, uint32_t extioIndex)
{
    string exp = ConfigParser::str_append_index("ExtioMod", extioIndex);
    int baudrate = EXTIO_COMMU_DEFAULT_BAUDRATE;
    parser->get(exp + ".Comm.Rtu.Baudrate", baudrate, true);
    int bits = EXTIO_COMMU_DEFAULT_BITS;
    parser->get(exp + ".Comm.Rtu.Bits", bits, true);
    int slaveId = extioIndex + 1;
    parser->get(exp + ".Comm.Rtu.SlaveId", slaveId, true);
    int stopbits = EXTIO_COMMU_DEFAULT_STOPBITS;
    parser->get(exp + ".Comm.Rtu.Stopbits", stopbits, true);
    int parity = EXTIO_COMMU_DEFAULT_PARITY;
    parser->get(exp + ".Comm.Rtu.Parity", parity, true);

    _handleRtuConfigOldVersion(parser, bits, stopbits, parity);

    //检查参数并赋值
    int ret = 0;
    if (!_checkBaudrate(baudrate))
    {
        _ELOG("Unsupport baudrate %d", baudrate);
        baudrate = EXTIO_COMMU_DEFAULT_BAUDRATE;
        ret = -1;
    }
    rtu.baudrate = baudrate;

    if (bits != 7 && bits != 8)
    {
        _ELOG("Unsupport bits %d", bits);
        bits = EXTIO_COMMU_DEFAULT_BITS;
        ret = -2;
    }
    rtu.databits = bits;

    if (stopbits != 1 && stopbits != 2)
    {
        _ELOG("Unsupport stopbits %d", stopbits);
        stopbits = EXTIO_COMMU_DEFAULT_STOPBITS;
        ret = -3;
    }
    rtu.stopbits = stopbits;

    if (parity != 78 && parity != 79 && parity != 69)
    {
        _ELOG("Unsupport parity %d", parity);
        parity = EXTIO_COMMU_DEFAULT_PARITY;
        ret = -4;
    }
    rtu.parity = parity;

    if (!_checkSlaveId(slaveId))
    {
        _ELOG("Unsupport slaveId %d", slaveId);
        slaveId = extioIndex + 1;
        ret = -5;
    }
    rtu.slaveId = slaveId;

    return ret;
}

int ExtioConfig::loadTcpComm(Util::ConfigParser* parser, NetComm& tcp, uint32_t extioIndex)
{
    string exp = ConfigParser::str_append_index("ExtioMod", extioIndex);
    string ip;
    parser->get(exp + ".Comm.Tcp.IP", ip, true);
    int port = EXTIO_COMMU_DEFAULT_PORT;
    parser->get(exp + ".Comm.Tcp.Port", port, true);
    int tcpSlaveID = extioIndex + 1;
    parser->get(exp + ".Comm.Tcp.tcpSlaveID", tcpSlaveID, true);
    //检查参数并赋值
    int ret = 0;
    if (!_checkIPAddrIsVaild(ip))
    {
        ip = EXTIO_COMMU_DEFAULT_IP;
        ret = -1;
    }
    strncpy(tcp.ipaddr, ip.c_str(), sizeof(tcp.ipaddr));
    if (port < 0 || port > EXTIO_COMMU_PORT_MAX)
    {
        port = EXTIO_COMMU_DEFAULT_PORT;
        ret = -2;
    }
    tcp.port = port;
    if (!_checkSlaveId(tcpSlaveID))
    {
        tcpSlaveID = extioIndex + 1;
        ret = -3;
    }
    tcp.slaveId = tcpSlaveID;

    return ret;
}

bool ExtioConfig::_checkIPAddrIsVaild(const string ipAddress)
{
    struct sockaddr_in sa;
    int result = inet_pton(AF_INET, ipAddress.c_str(), &(sa.sin_addr));
    return result == 1;
}
bool ExtioConfig::_checkBaudrate(int baudrate)
{
    //TODO:根据控制柜类型区分
    switch (baudrate)
    {
    case 4800:
    case 9600:
    case 19200:
    case 38400:
    case 57600:
    case 115200:
        return true;
    default:
        return false;
    }
    return false;
}
bool ExtioConfig::_checkSlaveId(int slaveId)
{
    if (slaveId < 0 || slaveId > EXTIO_COMMU_SLAVEID_MAX)
    {
        return false;
    }
    return true;
}

int ExtioConfig::iniLoadExtioReconnectParams(const char* cfgfile, int* reconnectInterval, int* reconnectMaxTimes)
{
    ZucIniFile trajInifile;
    if (trajInifile.Open(cfgfile))
    {
        if (ZucIniFile::ERR_NONE != trajInifile.Find(reconnectInterval, "INTERVAL", "EXTIORECONNECT"))
        {
            _ELOG(" read EXTIORECONNECT->INTERVAL failed!\n");
            *reconnectInterval = 0;
        }
        if (ZucIniFile::ERR_NONE != trajInifile.Find(reconnectMaxTimes, "MAXRECONNECT", "EXTIORECONNECT"))
        {
            _ELOG(" read EXTIORECONNECT->MAXRECONNECT failed!\n");
            *reconnectMaxTimes = 0;
        }
        if (*reconnectInterval < 0)
        {
            _ELOG("WARNNING : invalid reconnectInterval=%d    [0,3000]\n", *reconnectInterval);
            *reconnectInterval = 0;
        }
        if (*reconnectInterval > EXTIO_COMMU_MAX_RECONNECTINTERVAL)
        {
            _ELOG("WARNNING : invalid reconnectInterval=%d    [0,3000]\n", *reconnectInterval);
            *reconnectInterval = EXTIO_COMMU_MAX_RECONNECTINTERVAL;
        }

        if (*reconnectMaxTimes < 0)
        {
            _ELOG("WARNNING : invalid reconnectMaxTimes=%d    [0,10]\n", *reconnectMaxTimes);
            *reconnectMaxTimes = 0;
        }
        if (*reconnectMaxTimes > EXTIO_COMMU_MAX_RECONNECTTIME)
        {
            _ELOG("WARNNING : invalid reconnectMaxTimes=%d    [0,10]\n", *reconnectMaxTimes);
            *reconnectMaxTimes = EXTIO_COMMU_MAX_RECONNECTTIME;
        }
    }
    return 0;
}

int ExtioConfig::iniSaveExtioReconnectParams(const char* cfgfile, int reconnectInterval, int reconnectMaxTimes)
{
    if ((reconnectInterval < 0 || reconnectInterval > 3000) || (reconnectMaxTimes < 0 || reconnectMaxTimes > 10))
    {
        _ELOG("save extio reconnect paramaters failed: invalid reconnectInterval=%d  [0,3000],  reconnectMaxTimes=%d  [0,10]\n",
              reconnectInterval,
              reconnectMaxTimes);
        return -1;
    }

    try
    {
        std::string filename = cfgfile;
        Util::IniParser p(cfgfile);
        p.set("EXTIORECONNECT.INTERVAL", reconnectInterval);
        p.set("EXTIORECONNECT.MAXRECONNECT", reconnectMaxTimes);
    }
    catch (std::exception& e)
    {
        _ELOG("%s\n", e.what());
        return -1;
    }
    return 0;
}
void ExtioConfig::_handleRtuConfigOldVersion(Util::ConfigParser* parser, int& bits, int& stopbits, int& parity)
{
    int version = EXTIO_CONFIG_VERSION;
    parser->get("Version", version, true);
    if (1 == version)
    {
        if (bits == 8)
        {
            bits = 8;
            stopbits = 1;
            parity = 78;
        }
        else
        {
            bits = 8;
            stopbits = 2;
            parity = 69;
        }
    }
}
int ExtioConfig::saveRtuComm(Util::ConfigParser* parser, ModRtuComm rtu, uint32_t extioIndex)
{
    string exp = ConfigParser::str_append_index("ExtioMod", extioIndex) + ".Comm";

    parser->add(exp + ".Type", EXTIO_COMMU_MBRTU, true);
    exp += ".Rtu";
    parser->add(exp + ".Baudrate", rtu.baudrate, true);
    parser->add(exp + ".Bits", rtu.databits, true);
    parser->add(exp + ".SlaveId", rtu.slaveId, true);
    parser->add(exp + ".Stopbits", rtu.stopbits, true);
    parser->add(exp + ".Parity", rtu.parity, true);
    return 0;
}
int ExtioConfig::saveTcpComm(Util::ConfigParser* parser, NetComm tcp, uint32_t extioIndex)
{
    string exp = ConfigParser::str_append_index("ExtioMod", extioIndex) + ".Comm";

    parser->add(exp + ".Type", EXTIO_COMMU_MBTCP, true);
    exp += ".Tcp";
    parser->add(exp + ".IP", tcp.ipaddr, true);
    parser->add(exp + ".Port", tcp.port, true);
    parser->add(exp + ".tcpSlaveID", tcp.slaveId, true);
    return 0;
}

int xmlSaveExtIOSetup(int extIOModCnt, const ExtIOSetup* extIOSetups)
{
    ExtioConfig config;
    return config.saveExtIoSetup(ZUC_USER_CONFIG_FILE(extioconfig.xml), extIOSetups, extIOModCnt);
}
